# utils.py
# -*- coding: utf-8 -*-

import os
import json
import logging
import config  # 导入配置模块以访问配置项
import time
import random
import string
from typing import Optional, Dict, Any, List # 引入类型提示
from werkzeug.utils import secure_filename as werkzeug_secure_filename

# 配置日志记录器
# 获取具名的 logger
logger = logging.getLogger(__name__)


def generate_task_id() -> str:
    """
    生成唯一的任务 ID (检测编号)。

    格式: YYYYMMDDHHMMSSmmm-RRRRRR
        - YYYYMMDDHHMMSSmmm: 年月日时分秒毫秒时间戳
        - RRRRRR: 6位随机大写字母和数字组合

    Returns:
        str: 生成的唯一任务 ID。
    """
    # 获取当前时间戳，格式为 YYYYMMDDHHMMSS + 毫秒
    timestamp = time.strftime("%Y%m%d%H%M%S") + f"{int(time.time() * 1000) % 1000:03d}"

    # 生成 6 位随机字符（大写字母和数字组合）
    random_chars = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))

    # 拼接时间戳和随机字符
    task_id = f"{timestamp}-{random_chars}"
    logger.debug(f"生成的任务 ID: {task_id}")
    return task_id


def build_result_path(base_dir: str, task_id: str, filename_suffix: str) -> str:
    """
    构建结果文件的完整路径，并确保对应的任务子目录存在。

    结果文件将存储在 base_dir 下以 task_id 命名的子目录中。
    """
    # 创建任务专属的结果子目录
    task_dir = os.path.join(base_dir, task_id)
    try:
        # exist_ok=True 表示如果目录已存在则不抛出错误
        os.makedirs(task_dir, exist_ok=True)
    except OSError as e:
        # 处理创建目录时可能发生的错误
        logger.error(f"创建任务结果目录失败: {task_dir}, 错误: {e}", exc_info=True)
        # 根据需要，这里可以抛出异常或返回特定错误指示
        raise  # 重新抛出异常，让上层调用者知道失败了

    # 构造完整的文件名
    filename = f"{task_id}{filename_suffix}"
    full_path = os.path.join(task_dir, filename)
    logger.debug(f"构建的结果文件路径: {full_path}")
    return full_path


def load_result(task_id: str) -> Optional[Dict[str, Any]]:
    """
    从 JSON 文件加载指定任务的检测结果指标。
    """
    # 使用 build_result_path 构造结果文件路径，确保逻辑统一
    try:
        result_file = build_result_path(config.RESULT_DIR, task_id, "_metrics.json")
    except OSError:
         # 如果 build_result_path 失败 (例如无法创建目录)，则直接返回 None
         return None

    if not os.path.exists(result_file):
        logger.warning(f"结果文件未找到: {result_file}")
        return None

    try:
        # 使用配置中定义的默认编码读取文件
        with open(result_file, 'r', encoding=config.DEFAULT_ENCODING) as f:
            data: Dict[str, Any] = json.load(f)
        logger.info(f"成功加载结果文件: {result_file}")
        return data
    except IOError as e:
        logger.error(f"读取结果文件时发生 IO 错误 (任务 ID: {task_id}): {e}", exc_info=True)
        return None
    except json.JSONDecodeError as e:
        logger.error(f"解析结果 JSON 文件失败 (任务 ID: {task_id}): {e}", exc_info=True)
        return None
    except Exception as e:
        # 捕获其他可能的未知错误
        logger.error(f"加载结果文件时发生未知错误 (任务 ID: {task_id}): {e}", exc_info=True)
        return None

def get_corpus_files() -> List[str]:
    """
    获取语料库目录 (config.CORPUS_DIR) 下所有文件的完整路径列表。
    会忽略 .DS_Store 和其他以 '.' 开头的隐藏文件。
    """
    corpus_files: List[str] = []
    corpus_dir = config.CORPUS_DIR

    if not os.path.isdir(corpus_dir):
        logger.warning(f"指定的语料库目录不存在或不是一个目录: {corpus_dir}")
        return []

    try:
        for filename in os.listdir(corpus_dir):
            # 关键的过滤条件：
            if filename.startswith('.') or filename == ".DS_Store": # 忽略所有隐藏文件和明确的 .DS_Store
                logger.debug(f"跳过隐藏文件或 .DS_Store: {filename}")
                continue

            file_path = os.path.join(corpus_dir, filename)
            if os.path.isfile(file_path):
                corpus_files.append(file_path)
            else:
                logger.debug(f"跳过非文件条目: {file_path}")

        if not corpus_files:
            logger.warning(f"语料库目录 '{corpus_dir}' 为空或不包含任何有效文件（已排除隐藏文件）。")
        else:
            logger.info(f"在 '{corpus_dir}' 中找到 {len(corpus_files)} 个有效文件。")

    except OSError as e:
        logger.error(f"访问语料库目录时出错: {corpus_dir}, 错误: {e}", exc_info=True)
        return []

    return corpus_files


def read_file_content(file_path: str) -> Optional[str]:
    """
    安全地读取文本文件的内容。

    会尝试使用多种常见编码 (优先使用 config.DEFAULT_ENCODING) 来打开文件。
    """
    if not os.path.exists(file_path):
        logger.error(f"尝试读取的文件不存在: {file_path}")
        return None
    if not os.path.isfile(file_path):
         logger.error(f"尝试读取的路径不是一个文件: {file_path}")
         return None

    # 定义尝试读取时使用的编码列表
    encodings_to_try: List[str] = [
        config.DEFAULT_ENCODING,  # 首先尝试配置的默认编码 (通常是 'utf-8')
        'gbk',                   # 简体中文常用编码
        'gb2312',                # 另一个简体中文编码
        'utf-8-sig',             # 带 BOM 的 UTF-8
        'latin-1'                # 作为最后的兼容性尝试
    ]
    content: Optional[str] = None

    for enc in encodings_to_try:
        try:
            with open(file_path, 'r', encoding=enc) as f:
                content = f.read()
            # 读取成功，记录使用的编码并跳出循环
            logger.debug(f"成功使用编码 '{enc}' 读取文件: {file_path}")
            break
        except UnicodeDecodeError:
            # 当前编码不匹配，继续尝试下一种
            logger.debug(f"使用编码 '{enc}' 读取文件失败 (UnicodeDecodeError): {file_path}")
            continue
        except IOError as e:
            # 文件存在但发生 IO 错误 (例如权限问题)
            logger.error(f"读取文件时发生 IO 错误 (编码: {enc}): {file_path}, 错误: {e}", exc_info=True)
            return None # IO 错误通常是致命的，直接返回失败
        except Exception as e:
            # 捕获其他可能的异常
            logger.error(f"读取文件时发生未知错误 (编码: {enc}): {file_path}, 错误: {e}", exc_info=True)
            return None # 未知错误也返回失败

    if content is None:
        logger.error(f"无法使用任何支持的编码成功读取文件: {file_path}")
        return None

    # 可选：进行一些基本的清理，例如移除可能的空字节
    # content = content.replace('\x00', '')

    return content


def general_allowed_file(filename: str, allowed_extensions: set) -> bool:
    """
    检查文件名对应的扩展名是否在给定的允许扩展名集合中。
    """
    if '.' not in filename or filename.rsplit('.', 1)[1] == '':
        return False
    ext = filename.rsplit('.', 1)[1].lower()
    is_allowed = ext in allowed_extensions
    logger.debug(f"检查文件扩展名: 文件名='{filename}', 扩展名='{ext}', 允许的扩展名集合={allowed_extensions}, 是否允许={is_allowed}")
    return is_allowed


def secure_filename(filename: str) -> str:
    """
    确保文件名安全。
    """
    return werkzeug_secure_filename(filename)